#!python3.3 # replace types.SimpleNamespace with your own Bunch class for 3.2 compatibility
# -*- coding: utf-8 -*-
"""
Copyright (c) 2012-2013, Andrew Berg
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * The name of the author may not be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

"""
Description goes here.
"""

import queue
import threading
import types

import pynoisebot

class PermissionDenied(Exception):
	"""
	Used to abort the current command processing if the user is not allowed to use the command.
	"""
	pass

class die_if_error:
	"""
	If an unhandled exception is raised in a thread, this decorator is useful for notifying the main thread.
	"""
	def __init__(self, name):
		self.thread_name = name

	def __call__(self, func):
		def wrapper(*args, **kwargs):
			try:
				func(*args, **kwargs)
			except BaseException as exc:
				die('The {} thread died!'.format(self.thread_name), exc)
		return wrapper

def admin_command(func):
	def wrapper(message_namespace):
		if message_namespace.sender_is_owner:
			func(message_namespace)
		else:
			send_notice(message_namespace.config_network_name, message_namespace.nick, 'Only an owner may use this command.')
			raise PermissionDenied
	return wrapper

def command_func1(message_namespace):
	pass
command_func1.help_message = ''
command_func1.usage_message = ''

def command_func2(message_namespace):
	pass
command_func2.help_message = ''
command_func2.usage_message = ''

def module_help(message_namespace):
	"""
	Every command function should have a help_message and usage_message attribute (see examples above).
	"""
	global module_name
	trigger = connected_servers[message_namespace.config_network_name].trigger
	if not message_namespace.message:
		send_privmsg(message_namespace.config_network_name, message_namespace.source, 'This is a template module and you should really change this to a useful help message.')
		send_privmsg(message_namespace.config_network_name, message_namespace.source, 'Type {0}commands {1} to see available commands.'.format(trigger, module_name))
		return
	try:
		command_func = commands_to_functions[message_namespace.message]
	except KeyError:
		print('Got a help request for an unregistered command... ({})'.format(message_namespace.message))
		return
	else:
		send_privmsg(message_namespace.config_network_name, message_namespace.source, command_func.help_message)
		send_privmsg(message_namespace.config_network_name, message_namespace.source, '{0}{1}{2}'.format(trigger, message_namespace.message, command_func.usage_message))

def send_privmsg(server_name, dest, message):
	outgoing_message_dicts.put({'action':'irc_message', 'server':connected_servers[server_name].config_network_name, 'message_type':'privmsg', 'args':dest, 'message':message})

def send_notice(server_name, dest, message):
	outgoing_message_dicts.put({'action':'irc_message', 'server':connected_servers[server_name].config_network_name, 'message_type':'notice', 'args':dest, 'message':message})

json_encoder = pynoisebot.json_encoder
json_decoder = pynoisebot.json_decoder

@die_if_error(name='Receive Messages From Bot')
def recv_messages_from_bot(connection):
	while alive:
		incoming_json = connection.recv()
		incoming_messages.put(json_decoder.decode(incoming_json))

@die_if_error(name='Send Messages To Bot')
def send_messages_to_bot(connection):
	while alive:
		outgoing_message_dict = outgoing_message_dicts.get()
		outgoing_message_json = json_encoder.encode(outgoing_message_dict)
		pynoisebot.send_message(connection, outgoing_message_json)
		outgoing_message_dicts.task_done()

@die_if_error(name='Incoming Messages')
def process_incoming_messages():
	"""
	print() should be replaced with calls to a logger you create.
	"""
	while alive:
		incoming_message = types.SimpleNamespace(**incoming_messages.get())
		print(incoming_message)
		if incoming_message.action == 'irc_message':
			try:
				command_func = commands_to_functions[incoming_message.command]
			except KeyError:
				print('Got a command that isn\'t in commands_to_functions: {}'.format(incoming_message.command))
			else:
				print('Got a command: {}'.format(incoming_message.command))
				threading.Thread(target=process_command, args=(command_func, incoming_message), daemon=False).start()
		elif incoming_message.action in ('error', 'die'):
			print('Error message from bot: {}'.format(incoming_message.reason))
			die()
		elif incoming_message.action == 'ping':
			pynoisebot.ping_bot(connection)
		elif incoming_message.action == 'server_info':
			if incoming_message.config_network_name in connected_servers.keys():
				connected_servers[incoming_message.config_network_name].update(incoming_message)
			else:
				connected_servers[incoming_message.config_network_name] = pynoisebot.ConnectedServer(incoming_message)
		elif incoming_message.action == 'channel_info':
			pass
		elif incoming_message.action == 'module_info':
			global module_name
			module_name = incoming_message.module_name
			print('Module name is {}'.format(module_name))
		else:
			print('Ignored invalid message from bot: {}'.format(incoming_message))
		incoming_messages.task_done()

# where your command names are mapped to the functions they call
commands_to_functions = {'command1':command_func1,
						 'command2':command_func2,
						 'help':module_help}

def process_command(func, message_namespace):
	try:
		func(message_namespace)
	except pynoisebot.PermissionDenied:
		pass # any other errors should be handled at this point - this exception aborts the call by propagating up to this point

def die(reason, exc):
	global alive
	global connection
	pynoisebot.send_die_message(connection, reason)
	alive = False
	connection.close()
	raise exc

alive = True
connection = pynoisebot.establish_connection(('localhost', 20153))
recv_messages_from_bot_thread = threading.Thread(target=recv_messages_from_bot, args=(connection,), daemon=True)
send_messages_to_bot_thread = threading.Thread(target=send_messages_to_bot, args=(connection,), daemon=True)
incoming_messages = queue.Queue()
outgoing_message_dicts = queue.Queue()
commands_to_register = frozenset(commands_to_functions.keys()).difference({'help'})
pynoisebot.register_module(conn=connection, recv_events=False, recv_commands=True, recv_message_types=(), commands=tuple(commands_to_register))
recv_messages_from_bot_thread.start()
send_messages_to_bot_thread.start()
connected_servers = {}
process_incoming_messages()